from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template
from google.appengine.ext import db
import os

from html import CheckBox, DropDown, RadioButton #importing the html wrappers
import validate #import validation
import cgi #for escaping html chars in forms
import re, time
import staticInits, tables
import random

class SectionTableEntry(object):
    '''models section with methods for html output of dropdowns for ta's and checkboxes for checked'''

    def __init__(self, name, id, instructor, numTas, unique, selectedList=None, checked=False):
        """
        construcs Coruse, defaulting selectedList=None and checked=False
        selectedList is list of length numTas, giving blank string or taName to be selected in dropdown for relevant ta
        pre: selectedList.size = numTas
        """
        self.name = name
        self.id = id
        self.instructor = instructor
        self.numTas = numTas
        self.selectedList = selectedList if selectedList is not None else ['' for ta in xrange(self.numTas)] #blank spaces for every potential ta
        assert len(self.selectedList) == numTas #selected list must be size of tas, blank where none should be selected
        self.checked = checked
        self.unique = unique
    def taDropDowns(self):
        '''returns StrList of DropDowns for every ta for section'''
        class StrList(list):
            '''local class that overloads list's __str__, printing the str() of each element instead of a comma separated list of objects'''
            def __str__(self):
                result = ""
                for element in self: result += str(element)
                return result

        dropDowns = []
        for i in xrange(self.numTas) : dropDowns.append(DropDown(options = AdminMatching.availableTas, selected = self.selectedList[i], name = "ta"+str(self.unique)+"##"+str(i+1)))
        return StrList(dropDowns)
    def checkBox(self):
        '''returns Checkbox with status of self.checked'''
        return CheckBox(checked = self.checked, name="check"+str(self.unique))

class SmpTa(object):
    def __init__(self, ta) :
        self.ta = ta
        self.qualified = []
        self.marriedTo = None
        
    def marry(self, section) :
        if self.marriedTo == None :
            self.marriedTo = section
            return None
        assert section is not self.marriedTo
        #if self.ta == 'Alagappan, Ahmadi':
        #print 'weee ' + str(self.ta) +'--'+ str(self.marriedTo.section) +'++'+ str(section.section)
        #print self.qualified
        if self.marriedTo.section not in self.qualified and section.section in self.qualified:
            #print 'weee ' + str(self.ta) +'--'+ str(self.marriedTo.section) +'++'+ str(section.section)
            #print self.qualified
            temp = self.marriedTo
            self.marriedTo = section
            temp.index += 1
            return temp
        section.index += 1
        return section
    
class SmpSection(object) :
    def __init__(self, section) :
        self.section = section
        self.taList = []
        self.index = 0
        
    def propose(self) :
        if self.index >= len(self.taList):return None
        return self.taList[self.index].marry(self)
    def mate(self) :
        if self.index >= len(self.taList): return None
        return self.taList[self.index].ta

class AdminMatching(webapp.RequestHandler):    
    availableTas = []
    def __init__(self):
        webapp.RequestHandler.__init__(self) #calling parent constructor
        self.currentState = tables.AppState.get_by_key_name('currentState')
        self.currentSemester = self.currentState.semester
        
        self.semesters = dict()
        for semester in db.GqlQuery("SELECT * FROM Semester"): self.semesters[str(semester)] = semester        

    def buildView(self):
        '''
        Defines members of AdminMatching that are sensitive to semester currently in view - self.viewSemester.
        Called by get and post.
        '''
        
        assert 'viewSemester' in dir(self) #self.viewSemester must be defined before calling this method.
        
        self.tas = dict()       #key = ta display name (last, first); value = related ta object from db
        for ta in db.GqlQuery("SELECT * FROM Ta WHERE semester = :semester AND submitted = :submittedVal", semester=self.viewSemester, submittedVal = True):
            self.tas[ta.lastName + ', ' + ta.firstName] = ta 
        
        self.sections = dict()  #key = sectionUnique, value = related section object from db
        for section in db.GqlQuery("SELECT * FROM Section WHERE semester = :semester", semester=self.viewSemester): 
            self.sections[section.uniqueNumber] = section
        
        self.tableRows = dict() #keys = sectionUnique, values  = related SectionTableEntry object (helps put sections on screen, reducing db calls)
        for sectionUnique in self.sections:
            sectionObj = self.sections[sectionUnique]
            self.tableRows[sectionUnique] = SectionTableEntry(name = sectionObj.course.name, id = sectionObj.course.identifier, instructor = sectionObj.instructor.lastName + ',' + sectionObj.instructor.firstName, numTas = sectionObj.numTas, unique=sectionUnique)
            assignedTas = [sectionTaPairing.ta for sectionTaPairing in sectionObj.taSections]
            if len(assignedTas) == self.tableRows[sectionUnique].numTas: #assigned the full number
                self.tableRows[sectionUnique].checked = True
                self.tableRows[sectionUnique].selectedList = [(ta.lastName+', '+ta.firstName) for ta in assignedTas]
                
        self.instructorPrefs = dict()
        for instructorPref in db.GqlQuery("SELECT * FROM InstructorPref"):
            self.instructorPrefs[instructorPref.section.uniqueNumber] = instructorPref
        
        AdminMatching.availableTas = self.tas
        
    def get(self):
        '''Run when the webpage is accessed through the URL'''
        if(self.currentState.time.name != 'Admin Match'):
            self.response.out.write(staticInits.notTimeStr(verb = 'matching'))
        else:
            self.viewSemester = self.currentSemester #default view of current semester on first visit
            self.buildView()
            
            self.renderTemplate()
        
    def post(self):
        '''Executed when the form is submitted'''
        changeViewSemester = self.request.get('semesters', default_value="no change")
        clicked = self.request.get('submit', default_value="submitNotClicked")
        
        #setup view
        if changeViewSemester != 'no change': #changed viewSemester
            self.viewSemester = self.semesters[changeViewSemester] #update
        else:
            self.viewSemester = self.semesters[self.request.get('viewSemester')] #didn't change viewSemester, use hidden input from form
        
        #build it
        self.buildView()
        
        if clicked == 'Suggest Matchings' :
            self.smp()            
        elif clicked == 'Save' :
            self.validate()
                
        self.renderTemplate()
    
    def orderTas(self, tas, section):
        ip = None
        if section in self.instructorPrefs:
            ip = self.instructorPrefs[section]
        temp = dict.fromkeys(tas)
        for smpTa in temp:
            score = 0
            ta = self.tas[smpTa.ta]
            for history in ta.taTaHistory:
                if self.sections[section].course.key() == history.taHistory.key():
                    score += 1
                    break
            if ip is not None:
                for spec in ta.taSpecializations:
                    if ip.specialization is not None and ip.specialization.key() == spec.specialization.key():
                        score += 1
                        break
                if ta.nativeEnglish == ip.nativeEnglish: score += 1
                for wanted in ip.wantedTaInstructorPrefs:
                    if ta.key() == wanted.ta.key():
                        score += 4
                        break
                for unwanted in ip.unwantedTaInstructorPrefs:
                    if ta.key() == unwanted.ta.key():
                        score -= 4
                        break
            temp[smpTa] = score
        out = temp.keys()
        out.sort(key=lambda x:temp[x])
        out.reverse()
        return out
    
    def smp(self):
        sections = []
        #for key in self.tas.keys():
        #    print key
        tas = [SmpTa(key) for key in self.tas.keys()]
        freeSections = []
        for row in self.tableRows.values():
            if row.checked:
                for ta in row.selectedList:
                    for x in tas:
                        #print x.ta, ta
                        if x.ta == ta:
                            tas.remove(x)
        for row in self.tableRows.values():
            if not row.checked:
                row.selectedList = []
                for i in xrange(row.numTas):
                    temp = SmpSection(row.unique)
                    temp.taList = self.orderTas(tas,row.unique)
                    sections.append(temp)
                    freeSections.append(temp)
        freeSections.sort(key = lambda x : random.random())
        for ta in tas:
            for taQualification in self.tas[ta.ta].taQualifications:
                for section in taQualification.qualification.sections:
                    if str(section.semester) == str(self.currentSemester) :
                        ta.qualified.append(section.uniqueNumber)
        while len(freeSections) > 0 :
            section = freeSections.pop()
            temp = section.propose()
            if temp is not None:
                freeSections.append(temp)
        
        for s in sections:
            self.tableRows[s.section].selectedList.append(s.mate())
        self.response.out.write('''Matchings suggested. Check approved matchings and press '<b><font color="red">Save</font></b>' to store ... <br />''')
        
    def validate(self) :
        '''called by post, performs validation of approved (checked) matches'''
        
        error = False #flag for errors in input
        errorSections = set() #holds string names of sections with errors

        args = self.request.arguments() #getting all entries from submitted form

        #populating checkboxes dict and badSelections list
        checkBoxes = dict()
        badSelections = []
        taList = []            
        for argument in args :
            if re.match('check', argument) :
                checkBoxes[argument] = "on"
            elif re.match('ta', argument) :
                taList.append(argument)
                if not validate.dropDown(self.request.get(argument)) :                       
                    badSelections.append(argument)

        checkedSectionUniques = [int(sectionUnique.split("check")[1]) for sectionUnique in checkBoxes]                

        #checking for ta's assigned more than once to any section
        taDict = dict() #dictionary of tafields:tanames
        #adding those tas in db to taDict
        for section in self.sections.values():
            taIndex = 1
            for taSection in section.taSections:
                ta = taSection.ta
                taDict['ta'+str(section.uniqueNumber)+'##'+str(taIndex)] = '%s, %s' % (str(ta.lastName), str(ta.firstName))
                taIndex += 1
        for taField in taList :
            courseUnique = int(taField.split('ta')[1].split('##')[0])
            if courseUnique in checkedSectionUniques:
                taDict[taField] = self.request.get(taField)
        duplicates = validate.coursesToTAs(taDict)
        if len(duplicates.keys()) > 0:
            for taName in duplicates :
                self.response.out.write('TA ' + taName + ' assigned more than once. Assigned uniques: ' + str(list(duplicates[taName])) + '.<br />')
                for badUnique in duplicates[taName]:                    
                    errorSections |= set([badUnique, ]) #adding to errorSections

        
        #noting bad selections to user if selection was checked for saving
        for selection in badSelections :
            temp = selection.split('ta')[1]     #stripping 'ta' off front
            taNumber = temp.split('##')[1]      #pulling ta number out
            sectionUnique = temp.split('##')[0]   #pulling sectionUnique out
            checkKey = 'check' + sectionUnique
            if checkKey in checkBoxes : #if checked
                self.response.out.write('Invalid selection of ta #' + taNumber + ' for unique '+ sectionUnique + '.<br />') #give notice
                errorSections |= set([int(sectionUnique), ])
        
        if len(errorSections) == 0 : #input was valid, let user know successful save and do db save.
            for sectionUnique in checkedSectionUniques:
                section = self.sections[sectionUnique]
                for sectionTaPairing in section.taSections:
                    sectionTaPairing.delete()           #deleting old pairings
                for number in xrange(section.numTas):   #adding new pairings
                    taIndex = number + 1        #didn't start indexing ta's at zero for more readable error messages.
                    chosenTaName = taDict['ta'+str(sectionUnique)+'##'+str(taIndex)]
                    chosenTa = self.tas[chosenTaName]
                    tables.TaSection(section = section, ta = chosenTa).put()
            self.response.out.write('Input valid. Saved... <br />')

        #making form sticky
        #only checked rows will be sticky, unchecked will be consistant with whatever is in database
        #when validation fails for a section(ie, sectionUnique in errorSections == True) the data for that section "sticks" but the its box is unchecked
        #when validation passes for a section the data for that section sticks
        for sectionUnique in self.tableRows :
            row = self.tableRows[sectionUnique]
            row.checked = (row.checked or row.unique in checkedSectionUniques) and row.unique not in errorSections
            for i in xrange(row.numTas):
                taName = "ta"+str(row.unique)+"##"+str(i+1)
                row.selectedList[i] = row.selectedList[i] if row.unique not in checkedSectionUniques else self.request.get(taName)
        
    def renderTemplate(self) :
            '''Renders the template file adminMatch.html'''

            #setting up template dictionary
            templateValues = {
                'table': self.tableRows.values(),
                'viewSemester': self.viewSemester, #for hidden form input
                'semesters': DropDown(options=self.semesters.keys(), name = "semesters", selected = str(self.viewSemester), submitOnChange = True, defaultOptionPresent = False), #for choosing 'view'
            }
            
            #telling it to use the template
            path = os.path.join(os.path.dirname(__file__), 'adminMatch.html')
            self.response.out.write(template.render(path, templateValues))
        
application = webapp.WSGIApplication([('/admin/match', AdminMatching)], debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()
